package gui;

import static gui.IOManager.readFileAsString;
import static gui.IOManager.saveFile;
import static gui.IOManager.readFileAsLines;

import java.util.ArrayList;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import parser.ParseController;
import parser.table.LALRTableBuilder;
import parser.table.TableBuilder;
import scanner.CommentChars;
import scanner.Scanner;
import structures.grammar.BNFGrammarReader;
import structures.grammar.GrammarReaderDummy;
import structures.grammar.GrammarReaderDummy2;
import structures.parserStructures.ParseError;
import structures.parserStructures.ParserOutput;
import structures.table.LRTable;
import structures.table.action.Action;
import structures.table.symboltable.GlobalItemInfoTable;
import structures.table.symboltable.ItemInfo;
import structures.tree.ParseTreeNode;

public class View {
	private static final String[] SOURCE_EXTENSIONS = { "*.txt", "*.cpp",
			".java", ".c", "*.*" };
	private static final String[] GRAMMAR_EXTENSIONS = { "*.txt", "*.*" };

	protected Shell shlParser;
	private Table gotoTableView;
	private Table actionTableView;
	private TextViewer sourceCodeView;
	private TextViewer grammarView;
	private Tree parseTree;

	// Components
	private String sourceCodeFileName;
	private String grammarFileName;
	private String keywordsFileName;
	private Composite mainWindowContent;
	private TabFolder tabs;
	private TabItem sourceCodeTab;
	private TabItem grammarTab;
	private TabItem tableTab;
	private SashForm actionAndGotoContainer;
	private TabFolder informationalTabs;
	private TabItem consoleTab;
	private List console;
	private MenuItem mntmNewItem;
	private MenuItem mntmOpenSource;
	private MenuItem mntmOpenGrammar;
	private MenuItem mntmSave;
	private MenuItem mntmSaveAs;
	private MenuItem menuFileExit;
	private MenuItem menuEdit;
	private Menu cascadeEdit;
	private MenuItem mntmCut;
	private MenuItem mntmCopy;
	private MenuItem mntmPaste;
	private MenuItem mntmUndo;
	private MenuItem mntmDelete;
	private MenuItem mntmSelectAll;
	private MenuItem mntmClear;
	private MenuItem mntmPascal;
	private MenuItem mntmCLike;

	// Parser
	private ParseController parseController;
	private java.util.List<String> keywords;

	/**
	 * Launch the application.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			View window = new View();
			window.open();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public View() {
		keywords = new ArrayList<String>();
		
		parseController = new ParseController();
		parseController.setBuilder(LALRTableBuilder.class);
	}

	/**
	 * Open the window.
	 */
	public void open() {
		Display display = Display.getDefault();

		createContents();
		shlParser.open();
		shlParser.layout();

		while (!shlParser.isDisposed()) {
			if (!display.readAndDispatch()) {
				display.sleep();
			}
		}
	}

	/**
	 * Create contents of the window.
	 */
	protected void createContents() {
		shlParser = new Shell();
		shlParser
				.setImage(SWTResourceManager
						.getImage("images\\7214.png"));
		shlParser.setSize(704, 487);
		shlParser.setText("Parser generator");
		shlParser.setLayout(new FillLayout(SWT.HORIZONTAL));

		mainWindowContent = new Composite(shlParser, SWT.NONE);
		mainWindowContent.setLayout(new FillLayout(SWT.HORIZONTAL));

		//
		initMenus();

		SashForm tabsAndConsole = new SashForm(mainWindowContent, SWT.NONE);
		tabsAndConsole.setOrientation(SWT.VERTICAL);

		tabs = new TabFolder(tabsAndConsole, SWT.NONE);
		tabs.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				int tabIndex = tabs.getSelectionIndex();
				boolean enable;

				if (tabIndex == 0 || tabIndex == 1) {
					// sourceCodeTab or grammarTab was selected.
					enable = true;
				} else {
					enable = false;
				}

				for (MenuItem item : cascadeEdit.getItems()) {
					item.setEnabled(enable);
				}

				if (enable) {
					TextViewer viewer = getSelectedView();

					if (viewer != null && viewer.isEmptyUndoList()) {
						mntmUndo.setEnabled(false);
					}
				}
			}
		});

		sourceCodeTab = new TabItem(tabs, SWT.NONE);
		sourceCodeTab.setText("Source Code");

		sourceCodeView = new TextViewer(tabs, SWT.BORDER | SWT.H_SCROLL
				| SWT.V_SCROLL);
		sourceCodeTab.setControl(sourceCodeView);

		grammarTab = new TabItem(tabs, SWT.NONE);
		grammarTab.setText("Grammar");

		grammarView = new TextViewer(tabs, SWT.BORDER | SWT.H_SCROLL
				| SWT.V_SCROLL);
		grammarTab.setControl(grammarView);

		tableTab = new TabItem(tabs, SWT.NONE);
		tableTab.setText("Table");

		actionAndGotoContainer = new SashForm(tabs, SWT.NONE);
		tableTab.setControl(actionAndGotoContainer);

		actionTableView = new Table(actionAndGotoContainer, SWT.BORDER
				| SWT.FULL_SELECTION);
		actionTableView.setHeaderVisible(true);
		actionTableView.setLinesVisible(true);

		gotoTableView = new Table(actionAndGotoContainer, SWT.BORDER
				| SWT.FULL_SELECTION);
		gotoTableView.setHeaderVisible(true);
		gotoTableView.setLinesVisible(true);
		actionAndGotoContainer.setWeights(new int[] { 1, 1 });

		TabItem parseTreeTab = new TabItem(tabs, SWT.NONE);
		parseTreeTab.setText("Parse Tree");

		parseTree = new Tree(tabs, SWT.BORDER);
		parseTreeTab.setControl(parseTree);

		informationalTabs = new TabFolder(tabsAndConsole, SWT.NONE);

		consoleTab = new TabItem(informationalTabs, SWT.NONE);
		consoleTab.setText("Console");

		console = new List(informationalTabs, SWT.BORDER);
		consoleTab.setControl(console);
		tabsAndConsole.setWeights(new int[] { 300, 126 });

	}

	private void initMenus() {
		Menu menu = new Menu(shlParser, SWT.BAR);
		shlParser.setMenuBar(menu);

		MenuItem menuFile = new MenuItem(menu, SWT.CASCADE);
		menuFile.setText("File");

		Menu cascadeFile = new Menu(menuFile);
		menuFile.setMenu(cascadeFile);

		mntmNewItem = new MenuItem(cascadeFile, SWT.NONE);
		mntmNewItem.setAccelerator(SWT.MOD1 | 'N');
		mntmNewItem.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				newDocument();
			}
		});
		mntmNewItem.setText("New program");

		mntmOpenSource = new MenuItem(cascadeFile, SWT.NONE);
		mntmOpenSource.setAccelerator(SWT.MOD1 | 'O');
		mntmOpenSource.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				openSource();
			}
		});
		mntmOpenSource
				.setImage(SWTResourceManager
						.getImage("images\\Folder.png"));
		mntmOpenSource.setText("Open source file...");

		mntmOpenGrammar = new MenuItem(cascadeFile, SWT.NONE);
		mntmOpenGrammar.setImage(SWTResourceManager.getImage("images\\Folder.png"));
		mntmOpenGrammar.setAccelerator(SWT.MOD2 | 'O');
		mntmOpenGrammar.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				openGrammar();
			}
		});
		mntmOpenGrammar.setText("Open grammar...");
		
		MenuItem mntmOpenKeywords = new MenuItem(cascadeFile, SWT.NONE);
		mntmOpenKeywords.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				openKeywords();
			}
		});
		mntmOpenKeywords.setImage(SWTResourceManager.getImage("images\\Folder.png"));
		mntmOpenKeywords.setText("Open keywords");

		new MenuItem(cascadeFile, SWT.SEPARATOR);

		mntmSave = new MenuItem(cascadeFile, SWT.NONE);
		mntmSave.setAccelerator(SWT.MOD1 | 'S');
		mntmSave.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				save();
			}
		});
		mntmSave.setImage(SWTResourceManager
				.getImage("images\\Save.png"));
		mntmSave.setText("Save");

		mntmSaveAs = new MenuItem(cascadeFile, SWT.NONE);
		mntmSaveAs.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				saveAs();
			}
		});
		mntmSaveAs.setText("Save as...");

		new MenuItem(cascadeFile, SWT.SEPARATOR);

		menuFileExit = new MenuItem(cascadeFile, SWT.NONE);
		menuFileExit
				.setImage(SWTResourceManager
						.getImage("images\\Exit.png"));
		menuFileExit.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				shlParser.close();
			}
		});
		menuFileExit.setText("Exit");

		menuEdit = new MenuItem(menu, SWT.CASCADE);
		menuEdit.setText("Edit");

		cascadeEdit = new Menu(menuEdit);
		menuEdit.setMenu(cascadeEdit);

		mntmCut = new MenuItem(cascadeEdit, SWT.NONE);
		mntmCut.setAccelerator(SWT.MOD1 | 'X');
		mntmCut.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				cut();
			}
		});
		mntmCut.setImage(SWTResourceManager
				.getImage("images\\Cut.png"));
		mntmCut.setText("Cut");

		mntmCopy = new MenuItem(cascadeEdit, SWT.NONE);
		mntmCopy.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				copy();
			}
		});
		mntmCopy.setAccelerator(SWT.MOD1 | 'C');
		mntmCopy.setImage(SWTResourceManager
				.getImage("images\\Copy.png"));
		mntmCopy.setText("Copy");

		mntmPaste = new MenuItem(cascadeEdit, SWT.NONE);
		mntmPaste.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				paste();
			}
		});
		mntmPaste.setAccelerator(SWT.MOD1 | 'V');
		mntmPaste
				.setImage(SWTResourceManager
						.getImage("images\\Paste.png"));
		mntmPaste.setText("Paste");

		mntmUndo = new MenuItem(cascadeEdit, SWT.NONE);
		mntmUndo.setAccelerator(SWT.MOD1 | 'Z');
		mntmUndo.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				undo();
			}
		});
		mntmUndo.setImage(SWTResourceManager
				.getImage("images\\Undo.png"));
		mntmUndo.setText("Undo");

		new MenuItem(cascadeEdit, SWT.SEPARATOR);

		mntmDelete = new MenuItem(cascadeEdit, SWT.NONE);
		mntmDelete.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				remove();
			}
		});
		mntmDelete
				.setImage(SWTResourceManager
						.getImage("images\\Delete.png"));
		mntmDelete.setText("Delete");

		mntmSelectAll = new MenuItem(cascadeEdit, SWT.NONE);
		mntmSelectAll.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				selectAll();
			}
		});

		mntmSelectAll.setText("Select All");
		mntmSelectAll.setAccelerator(SWT.MOD1 | 'A');

		mntmClear = new MenuItem(cascadeEdit, SWT.NONE);
		mntmClear.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				clear();
			}
		});
		mntmClear.setText("Clear");

		MenuItem mntmParse_1 = new MenuItem(menu, SWT.CASCADE);
		mntmParse_1.setText("Run");

		Menu cascadeRun = new Menu(mntmParse_1);
		mntmParse_1.setMenu(cascadeRun);

		MenuItem mntmParse = new MenuItem(cascadeRun, SWT.NONE);
		mntmParse.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				parse();
			}
		});
		mntmParse
				.setImage(SWTResourceManager
						.getImage("images\\Play.png"));
		mntmParse.setText("Parse");

		MenuItem mntmTable = new MenuItem(cascadeRun, SWT.CASCADE);
		mntmTable
				.setImage(SWTResourceManager
						.getImage("images\\Table.png"));
		mntmTable.setText("Table");

		Menu menu_7 = new Menu(mntmTable);
		mntmTable.setMenu(menu_7);

		MenuItem mntmBuild = new MenuItem(menu_7, SWT.NONE);
		mntmBuild.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				build();
			}
		});
		mntmBuild.setText("Build");

		MenuItem mntmShowTables = new MenuItem(menu_7, SWT.NONE);
		mntmShowTables.setText("Show tables");

		MenuItem menuPreferenses = new MenuItem(menu, SWT.CASCADE);
		menuPreferenses.setText("Preferences");

		Menu cascadePreferences = new Menu(menuPreferenses);
		menuPreferenses.setMenu(cascadePreferences);

		MenuItem mntmParsingAlgorithm = new MenuItem(cascadePreferences,
				SWT.CASCADE);
		mntmParsingAlgorithm
				.setImage(SWTResourceManager
						.getImage("images\\Pinion.png"));
		mntmParsingAlgorithm.setText("Parsing algorithm");

		Menu menu_5 = new Menu(mntmParsingAlgorithm);
		mntmParsingAlgorithm.setMenu(menu_5);

		MenuItem mntmLl = new MenuItem(menu_5, SWT.RADIO);
		mntmLl.setEnabled(false);
		mntmLl.setText("LL(1)");

		MenuItem mntmLalr = new MenuItem(menu_5, SWT.RADIO);
		mntmLalr.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				parseController.setBuilder(LALRTableBuilder.class);
			}
		});
		mntmLalr.setSelection(true);
		mntmLalr.setText("LALR(1)");

		MenuItem mntmLr = new MenuItem(menu_5, SWT.RADIO);
		mntmLr.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				parseController.setBuilder(TableBuilder.class);
			}
		});
		mntmLr.setText("LR(1)");
		
		MenuItem mntmCommentsType = new MenuItem(cascadePreferences, SWT.CASCADE);
		mntmCommentsType.setText("Comments type");
		
		Menu menu_1 = new Menu(mntmCommentsType);
		mntmCommentsType.setMenu(menu_1);
		
		mntmPascal = new MenuItem(menu_1, SWT.RADIO);
		mntmPascal.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
			}
		});
		mntmPascal.setSelection(true);
		mntmPascal.setText("Pascal like");
		
		mntmCLike = new MenuItem(menu_1, SWT.RADIO);
		mntmCLike.setText("C like");

		MenuItem menuHelp = new MenuItem(menu, SWT.CASCADE);
		menuHelp.setText("Help");

		Menu cascadeAbout = new Menu(menuHelp);
		menuHelp.setMenu(cascadeAbout);

		MenuItem menuHelpAbout = new MenuItem(cascadeAbout, SWT.NONE);
		menuHelpAbout.setText("About");
	}

	private String getOpenDialogFile(String dialogText, String[] extensions) {
		FileDialog dialog = new FileDialog(shlParser, SWT.OPEN);

		dialog.setText(dialogText);
		dialog.setFilterExtensions(extensions);

		return dialog.open();
	}

	private void save() {
		int selectedTabIndex = tabs.getSelectionIndex();

		switch (selectedTabIndex) {
		case 0:
			if (sourceCodeFileName != null
					|| (sourceCodeFileName = getOpenDialogFile(
							"Save source code as...", SOURCE_EXTENSIONS)) != null) {
				saveFile(sourceCodeFileName, sourceCodeView.getText());
				console.add("Saved changes in source code: "
						+ sourceCodeFileName);
			}
			break;
		case 1:
			if (grammarFileName != null
					|| (grammarFileName = getOpenDialogFile(
							"Save grammar as...", GRAMMAR_EXTENSIONS)) != null) {
				saveFile(grammarFileName, grammarView.getText());
				console.add("Saved changes in grammar: " + grammarFileName);
			}
			break;
		default:
			break;
		}
	}

	private void saveAs() {
		int selectedTabIndex = tabs.getSelectionIndex();
		String fileName;

		switch (selectedTabIndex) {
		case 0:
			if ((fileName = getOpenDialogFile("Save source code as...",
					SOURCE_EXTENSIONS)) != null) {
				sourceCodeFileName = fileName;
				saveFile(sourceCodeFileName, sourceCodeView.getText());
				console.add("Saved source code to " + sourceCodeFileName);
			}
			break;
		case 1:
			if ((fileName = getOpenDialogFile("Save grammar as...",
					GRAMMAR_EXTENSIONS)) != null) {
				grammarFileName = fileName;
				saveFile(grammarFileName, grammarView.getText());
				console.add("Saved grammar to " + grammarFileName);
			}
			break;
		default:
			break;
		}
	}

	private void newDocument() {
		sourceCodeView.clear();
		console.add("Cleared source code");
	}

	private void openSource() {
		String fileName = getOpenDialogFile("Open Source File...",
				SOURCE_EXTENSIONS);

		if (fileName != null) {
			sourceCodeFileName = fileName;
			sourceCodeView.setText(readFileAsString(sourceCodeFileName));
			sourceCodeView.clearUndoList();
			console.add("Opened source code file: " + fileName);
		}
	}

	private void openGrammar() {
		String fileName = getOpenDialogFile("Open Grammar File...",
				GRAMMAR_EXTENSIONS);

		if (fileName != null) {
			grammarFileName = fileName;
			grammarView.setText(readFileAsString(grammarFileName));
			grammarView.clearUndoList();
			console.add("Opened grammar file: " + fileName);
		}
	}

	private TextViewer getSelectedView() {
		int selectedTab = tabs.getSelectionIndex();

		switch (selectedTab) {
		case 0:
			return sourceCodeView;
		case 1:
			return grammarView;
		default:
			return null;
		}
	}

	private void cut() {
		TextViewer view = getSelectedView();

		if (view != null) {
			view.cut();
		}
	}

	private void paste() {
		TextViewer view = getSelectedView();

		if (view != null) {
			view.paste();
		}
	}

	private void copy() {
		TextViewer view = getSelectedView();

		if (view != null) {
			view.copy();
		}
	}

	private void undo() {
		TextViewer view = getSelectedView();

		if (view != null) {
			view.undo();
		}
	}

	private void remove() {
		TextViewer view = getSelectedView();

		if (view != null) {
			view.remove();
		}
	}

	private void selectAll() {
		TextViewer view = getSelectedView();

		if (view != null) {
			view.selectAll();
		}
	}

	private void clear() {
		TextViewer view = getSelectedView();

		if (view != null) {
			view.clear();
		}
	}
	
	private void build() {
		
	}

	private void parse() {
		if (sourceCodeView.getText().equals("")) {
			console.add("Error! Source code is empty!");
		} else if (grammarView.getText().equals("")) {
			console.add("Error! Grammar is empty!");
		} else {
			
			parseController.setSourceCode(sourceCodeView.getText());
			parseController.setGrammar(grammarView.getText());
			GlobalItemInfoTable globalTable = new GlobalItemInfoTable(keywords);
			parseController.setScanner(new Scanner(globalTable, getComments()));
			parseController.setReader(new BNFGrammarReader(globalTable));
			
			ParserOutput out = parseController.parse();
			if (out.err == ParseError.OK) {
				TreeItem item = new TreeItem(parseTree, SWT.NONE);
				item.setText(out.tree.getElem().getName());
				traverseTree(item, out.tree);
				buildTable(actionTableView, parseController.getActions());
				buildTable(gotoTableView, parseController.getGotos());
				console.add("Parse successful.");
			} else {
				console.add("Parse error!");
			}

		}
	}

	private void traverseTree(TreeItem root, ParseTreeNode data) {

		for (ParseTreeNode node : data.getLeaves()) {
			TreeItem child = new TreeItem(root, SWT.NONE);
			child.setText(node.getElem().getName());
			if (node.getLeaves() != null) {
				traverseTree(child, node);
			}
		}

	}

	private void buildTable(Table table, LRTable lrTable) {
		java.util.List<ItemInfo> columnNames = lrTable.getColumns();
		// create table columns
		
		TableColumn stateRow = new TableColumn(table, SWT.CENTER);
		stateRow.setText("State");
		
		for (ItemInfo item : columnNames) {
			TableColumn column = new TableColumn(table, SWT.CENTER);
			column.setText(item.getName());
		}

		for (int colIndex = 0; colIndex < lrTable.getStateAmount(); ++colIndex) {
			TableItem item = new TableItem(table, SWT.NONE);
			
			item.setText(0, String.valueOf(colIndex));
			
			for (int rowIndex = 1; rowIndex <= columnNames.size(); ++rowIndex) {
				Action<?> action = lrTable.get(colIndex, columnNames.get(rowIndex - 1));
				
				if (action == null) {
					continue;
				} else {
					item.setText(rowIndex, action.toString());
				}
			}
		}
		
		for (TableColumn column : table.getColumns()) {
			column.pack();
		}
	}
	
	private void openKeywords() {
		String fileName = getOpenDialogFile("Open Keywords File...",
				GRAMMAR_EXTENSIONS);
		
		if (fileName != null) {
			keywordsFileName = fileName;
			console.add("Opened keywords file: " + fileName);
		}
		
		keywords = readFileAsLines(keywordsFileName);
	}
	
	private CommentChars getComments() {
		if (mntmPascal.getSelection()) {
			return CommentChars.pascalCommentChars;
		} else {
			return CommentChars.cCommentChars;
		}
	}
}
